{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "aec859a2",
   "metadata": {},
   "source": [
    "# HTMLHeaderTextSplitter\n",
    "\n",
    "<a href=\"https://python.langchain.com/docs/modules/data_connection/document_transformers/text_splitters/markdown_header_metadata\">`MarkdownHeaderTextSplitter`</a>와 개념적으로 유사한 `HTMLHeaderTextSplitter`는 텍스트를 요소 수준에서 분할하고 각 헤더에 대한 메타데이터를 추가하는 \"구조 인식\" 청크 생성기입니다.\n",
    "\n",
    "이는 각 청크와 \"관련된\" 메타데이터를 추가합니다.\n",
    "\n",
    "`HTMLHeaderTextSplitter`는 요소별로 청크를 반환하거나 동일한 메타데이터를 가진 요소를 결합할 수 있으며\n",
    "\n",
    "- (a) 관련 텍스트를 의미론적으로 (대략적으로) 그룹화하고\n",
    "- (b) 문서 구조에 인코딩된 컨텍스트 풍부한 정보를 보존하는 것을 목표로 합니다.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "079cc73e",
   "metadata": {},
   "source": [
    "## HTML 문자열을 사용하는 경우\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8d6331f3",
   "metadata": {},
   "source": [
    "- `headers_to_split_on` 리스트에 분할 기준이 되는 헤더 태그와 해당 헤더의 이름을 튜플 형태로 지정합니다.\n",
    "- `HTMLHeaderTextSplitter` 객체를 생성하면서 `headers_to_split_on` 매개변수에 분할 기준 헤더 리스트를 전달합니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d71c85ac",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_text_splitters import HTMLHeaderTextSplitter\n",
    "\n",
    "html_string = \"\"\"\n",
    "<!DOCTYPE html>\n",
    "<html>\n",
    "<body>\n",
    "    <div>\n",
    "        <h1>헤더1</h1>\n",
    "        <p>헤더1 에 포함된 본문</p>\n",
    "        <div>\n",
    "            <h2>헤더2-1 제목</h2>\n",
    "            <p>헤더2-1 에 포함된 본문</p>\n",
    "            <h3>헤더3-1 제목</h3>\n",
    "            <p>헤더3-1 에 포함된 본문</p>\n",
    "            <h3>헤더3-2 제목</h3>\n",
    "            <p>헤더3-2 에 포함된 본문</p>\n",
    "        </div>\n",
    "        <div>\n",
    "            <h2>헤더2-2 제목2</h2>\n",
    "            <p>헤더2-2 에 포함된 본문</p>\n",
    "        </div>\n",
    "        <br>\n",
    "        <p>마지막 내용</p>\n",
    "    </div>\n",
    "</body>\n",
    "</html>\n",
    "\"\"\"\n",
    "\n",
    "headers_to_split_on = [\n",
    "    (\"h1\", \"Header 1\"),  # 분할할 헤더 태그와 해당 헤더의 이름을 지정합니다.\n",
    "    (\"h2\", \"Header 2\"),\n",
    "    (\"h3\", \"Header 3\"),\n",
    "]\n",
    "\n",
    "# 지정된 헤더를 기준으로 HTML 텍스트를 분할하는 HTMLHeaderTextSplitter 객체를 생성합니다.\n",
    "html_splitter = HTMLHeaderTextSplitter(headers_to_split_on=headers_to_split_on)\n",
    "# HTML 문자열을 분할하여 결과를 html_header_splits 변수에 저장합니다.\n",
    "html_header_splits = html_splitter.split_text(html_string)\n",
    "# 분할된 결과를 출력합니다.\n",
    "for header in html_header_splits:\n",
    "    print(f\"{header.page_content}\")\n",
    "    print(f\"{header.metadata}\", end=\"\\n=====================\\n\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b6d970ec",
   "metadata": {},
   "source": [
    "## 다른 splitter와 파이프라인으로 연결하고, 웹 URL에서 HTML을 로드하는 경우입니다.\n",
    "\n",
    "이 예시에서는 웹 URL로부터 HTML 콘텐츠를 로드한 후, 이를 다른 splitter와 파이프라인으로 연결하여 처리하는 과정입니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9c51d85c",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_text_splitters import RecursiveCharacterTextSplitter\n",
    "\n",
    "url = \"https://plato.stanford.edu/entries/goedel/\"  # 분할할 텍스트의 URL을 지정합니다.\n",
    "\n",
    "headers_to_split_on = [  # 분할할 HTML 헤더 태그와 해당 헤더의 이름을 지정합니다.\n",
    "    (\"h1\", \"Header 1\"),\n",
    "    (\"h2\", \"Header 2\"),\n",
    "    (\"h3\", \"Header 3\"),\n",
    "    (\"h4\", \"Header 4\"),\n",
    "]\n",
    "\n",
    "# HTML 헤더를 기준으로 텍스트를 분할하는 HTMLHeaderTextSplitter 객체를 생성합니다.\n",
    "html_splitter = HTMLHeaderTextSplitter(headers_to_split_on=headers_to_split_on)\n",
    "\n",
    "# URL에서 텍스트를 가져와 HTML 헤더를 기준으로 분할합니다.\n",
    "html_header_splits = html_splitter.split_text_from_url(url)\n",
    "\n",
    "chunk_size = 500  # 텍스트를 분할할 청크의 크기를 지정합니다.\n",
    "chunk_overlap = 30  # 분할된 청크 간의 중복되는 문자 수를 지정합니다.\n",
    "text_splitter = RecursiveCharacterTextSplitter(  # 텍스트를 재귀적으로 분할하는 RecursiveCharacterTextSplitter 객체를 생성합니다.\n",
    "    chunk_size=chunk_size, chunk_overlap=chunk_overlap\n",
    ")\n",
    "\n",
    "# HTML 헤더로 분할된 텍스트를 다시 청크 크기에 맞게 분할합니다.\n",
    "splits = text_splitter.split_documents(html_header_splits)\n",
    "\n",
    "# 분할된 텍스트 중 80번째부터 85번째까지의 청크를 출력합니다.\n",
    "for header in splits[80:85]:\n",
    "    print(f\"{header.page_content}\")\n",
    "    print(f\"{header.metadata}\", end=\"\\n=====================\\n\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4362cc91",
   "metadata": {},
   "source": [
    "## 한계\n",
    "\n",
    "HTMLHeaderTextSplitter는 HTML 문서 간의 구조적 차이를 처리하려고 시도하지만, 때로는 특정 헤더를 누락할 수 있습니다.\n",
    "\n",
    "예를 들어, 이 알고리즘은 헤더가 항상 관련 텍스트보다 \"위\"에 있는 노드, 즉 이전 형제 노드, 조상 노드 및 이들의 조합에 위치한다고 가정합니다.\n",
    "\n",
    "다음 뉴스 기사(이 문서 작성 시점 기준)에서는 최상위 헤드라인의 텍스트가 \"h1\"으로 태그되어 있지만, 우리가 예상하는 텍스트 요소와는 **별개의 하위 트리** 에 있는 것을 볼 수 있습니다.\n",
    "\n",
    "따라서 \"h1\" 요소와 관련 텍스트는 청크 메타데이터에 나타나지 않지만, 해당되는 경우 \"h2\"와 관련 텍스트는 볼 수 있습니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "147313da",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 분할할 HTML 페이지의 URL을 지정합니다.\n",
    "url = \"https://www.cnn.com/2023/09/25/weather/el-nino-winter-us-climate/index.html\"\n",
    "\n",
    "headers_to_split_on = [\n",
    "    (\"h1\", \"Header 1\"),  # 분할할 헤더 태그와 해당 헤더의 이름을 지정합니다.\n",
    "    (\"h2\", \"Header 2\"),  # 분할할 헤더 태그와 해당 헤더의 이름을 지정합니다.\n",
    "]\n",
    "\n",
    "# 지정된 헤더를 기준으로 HTML 텍스트를 분할하는 HTMLHeaderTextSplitter 객체를 생성합니다.\n",
    "html_splitter = HTMLHeaderTextSplitter(headers_to_split_on=headers_to_split_on)\n",
    "\n",
    "# 지정된 URL의 HTML 페이지를 분할하여 결과를 html_header_splits 변수에 저장합니다.\n",
    "html_header_splits = html_splitter.split_text_from_url(url)\n",
    "\n",
    "# 분할된 결과를 출력합니다.\n",
    "for header in html_header_splits:\n",
    "    print(f\"{header.page_content[:100]}\")\n",
    "    print(f\"{header.metadata}\", end=\"\\n=====================\\n\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "py-test",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
